
#include "Solver.h"

void Solver::solve(Node* root) {
    solve(root, VERTICAL);
    if(root->get_width(alpha, beta) > width)
        solve(root, HORIZONTAL);
}

void Solver::calculate_node(CvMat* A, CvMat* B, Node* no, Node* raiz, map<Node*, int>* mapa, Orientation orientacaoSolucao, bool oposto) {
    if(no->is_leaf()) {
        if(oposto) {
            double val = orientacaoSolucao == raiz->get_orientation() ? -1.0 : -(no->get_image()->get_aspect_ratio());
            cvmSet(A, mapa->at(raiz), mapa->at(no), val);
            val = cvmGet(B, mapa->at(raiz), 0) + 2.0*beta;
            cvmSet(B, mapa->at(raiz), 0, val);
        } else {
            double val = orientacaoSolucao == raiz->get_orientation() ? 1.0 : no->get_image()->get_aspect_ratio();
            cvmSet(A, mapa->at(raiz), mapa->at(no), val);
            val = cvmGet(B, mapa->at(raiz), 0) - 2.0*beta;
            cvmSet(B, mapa->at(raiz), 0, val);
        }
    } else {
        if(no->get_orientation() == raiz->get_orientation()) {
            calculate_node(A, B, no->get_left(), raiz, mapa, orientacaoSolucao, oposto);
        } else {
            double val = cvmGet(B, mapa->at(raiz), 0);
            val += oposto ? alpha : -alpha;
            cvmSet(B, mapa->at(raiz), 0, val);
            calculate_node(A, B, no->get_left(), raiz, mapa, orientacaoSolucao, oposto);
            calculate_node(A, B, no->get_right(), raiz, mapa, orientacaoSolucao, oposto);
        }
    }
}

void Solver::calculate_bissections(CvMat* A, CvMat* B, Node* raiz, map<Node*, int>* mapa, Orientation orientacaoSolucao) {
    if(raiz->get_type() == BISS) {
        calculate_bissections(A, B, raiz->get_left(), mapa, orientacaoSolucao);
        calculate_bissections(A, B, raiz->get_right(), mapa, orientacaoSolucao);
        calculate_node(A, B, raiz->get_left(), raiz, mapa, orientacaoSolucao, false);
        calculate_node(A, B, raiz->get_right(), raiz, mapa, orientacaoSolucao, true);
    }
}

/*
 Needed?
 */
/*int aprox(double d) {
    return ((int)d) + ((d - ((int)d)) > 0.5 ? 1 : 0);
}*/
int Solver::aprox(double d) {
    return (int)d;
}

void Solver::apply_dimensions(Node* raiz, map<Node*, int>* mapa, CvMat* x, Orientation orientation) {
    if(raiz->is_leaf()) {
        if(orientation == HORIZONTAL) {
            double width = cvmGet(x, mapa->at(raiz), 0);
            double height = width * raiz->get_image()->get_aspect_ratio();
            raiz->get_image()->set_dimensions(aprox(width), aprox(height));
        } else {
            double height = cvmGet(x, mapa->at(raiz), 0);
            double width = height / raiz->get_image()->get_aspect_ratio();
            raiz->get_image()->set_dimensions(aprox(width), aprox(height));
        }
    } else {
        apply_dimensions(raiz->get_left(), mapa, x, orientation);
        apply_dimensions(raiz->get_right(), mapa, x, orientation);
    }
}

void Solver::modify(Node* root, double a) {
    if(root->is_leaf()) {
        root->get_image()->set_dimensions(
                root->get_image()->get_width() * a,
                root->get_image()->get_height() * a);
    } else {
        if(root->get_left()->is_leaf() && !root->get_right()->is_leaf()) {
            double c = 0.0;
            if(root->get_orientation() == HORIZONTAL) {
                c = (a*root->get_left()->get_width(alpha, beta) - alpha - 2*beta) / (root->get_right()->get_left()->get_width(alpha, beta) + root->get_right()->get_right()->get_width(alpha, beta));
            } else {
                c = (a*root->get_left()->get_height(alpha, beta) - alpha - 2*beta) / (root->get_right()->get_left()->get_height(alpha, beta) + root->get_right()->get_right()->get_height(alpha, beta));
            }
            modify(root->get_left(), a);
            modify(root->get_right(), c);
        } else if(!root->get_left()->is_leaf() && root->get_right()->is_leaf()) {
            double c = 0.0;
            if(root->get_orientation() == HORIZONTAL) {
                c = (a*root->get_right()->get_width(alpha, beta) - alpha - 2*beta) / (root->get_left()->get_left()->get_width(alpha, beta) + root->get_left()->get_right()->get_width(alpha, beta));
            } else {
                c = (a*root->get_right()->get_height(alpha, beta) - alpha - 2*beta) / (root->get_left()->get_left()->get_height(alpha, beta) + root->get_left()->get_right()->get_height(alpha, beta));
            }
            modify(root->get_left(), c);
            modify(root->get_right(), a);
        } else {
            modify(root->get_left(), a);
            modify(root->get_right(), a);
        }
    }
}

void Solver::fix(Node* root) {
    int r_w = root->get_width(alpha, beta);
    int r_h = root->get_height(alpha, beta);
    int d_w = r_w - width;
    int d_h = r_h - height;
    if(d_w > d_h && d_w > 0) {
        modify(root, width / (double)r_w);
    } else if(d_h > d_w && d_h > 0) {
        modify(root, height / (double)r_h);
    }
}

void Solver::solve(Node* root, Orientation orientation) {
    map<Node*, int>* mapa = new map<Node*, int>();
    this->mapear(root, mapa);
    int size = mapa->size()/2 + 1;
    CvMat* A = cvCreateMat(size, size, CV_32FC1);
    CvMat* B = cvCreateMat(size, 1, CV_32FC1);
    CvMat* x = cvCreateMat(size, 1, CV_32FC1);
    for(int i = 0; i < size; i++) {
        for(int j = 0; j < size; j++) {
            cvmSet(A, i, j, 0);
        }
        cvmSet(B, i, 0, 0);
    }
    Node* temp = new Node(root, orientation, root);
    mapa->insert(pair<Node*, int>(temp, size-1));
    calculate_bissections(A, B, root, mapa, orientation);
    cvmSet(B, size-1, 0, (double)(orientation == HORIZONTAL ? width : height));
    calculate_node(A, B, root, temp, mapa, orientation, false);
    cvSolve(A, B, x);
    apply_dimensions(root, mapa, x, orientation);
    root->set_father(NULL);
//    fix(root);
}

void Solver::mapear(Node* raiz, map<Node*, int>* mapa) {
    mapa->clear();
    vector<Node*> lista;
    int bissectionNum = 0;
    int leafNum = 0;
    lista.push_back(raiz);
    while(!lista.empty()) {
        if(lista.front()->get_type() == BISS) {
            lista.push_back(lista.front()->get_left());
            lista.push_back(lista.front()->get_right());
            mapa->insert(pair<Node*, int>(lista.front(), bissectionNum++));
        } else {
            mapa->insert(pair<Node*, int>(lista.front(), leafNum++));
        }
        lista.erase(lista.begin());
    }
}

